package com.gitee.ixtf.mongo

import com.gitee.ixtf.core.EntitiesDTO
import com.gitee.ixtf.core.EntityDTO
import com.gitee.ixtf.guice.Jguice
import com.gitee.ixtf.mongo.Jmongo.Companion.asFilter
import com.gitee.ixtf.mongo.Jmongo.Companion.idIn
import com.gitee.ixtf.mongo.kotlinx.*
import com.mongodb.client.model.BulkWriteOptions
import com.mongodb.client.model.WriteModel
import com.mongodb.kotlin.client.coroutine.MongoCollection
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import org.bson.BsonDocument
import org.bson.Document
import org.bson.conversions.Bson
import org.bson.types.ObjectId
import java.lang.reflect.ParameterizedType
import java.security.Principal

interface CurdRepository<T : MongoEntityBase> {
  val entityClass: Class<T>
  val jmongo: Jmongo
  val collection: MongoCollection<Document>
  val bsonCollection: MongoCollection<BsonDocument>
  val entityCollection: MongoCollection<T>

  suspend fun save(entity: T) =
      if (entityCollection.exists(entity.id)) update(entity) else insert(entity)

  suspend fun bulkWrite(
      requests: List<WriteModel<out T>>,
      options: BulkWriteOptions = BulkWriteOptions()
  ) = entityCollection.bulkWrite(requests, options)

  suspend fun insert(entity: T): Unit = jmongo.uow { registerNew(entity) }

  suspend fun update(entity: T): Unit = jmongo.uow { registerDirty(entity) }

  suspend fun delete(entity: T): Unit = jmongo.uow { registerDelete(entity) }

  suspend fun purge(entity: T): Unit = jmongo.uow { registerPurge(entity) }

  suspend fun findOrNull(id: String) = entityCollection.singleOrNull(id)

  suspend fun find(id: String) = entityCollection.single(id)

  suspend fun findOrCreate(id: String, block: suspend () -> T) = findOrNull(id) ?: block()

  suspend fun findOrNull(id: ObjectId) = entityCollection.singleOrNull(id)

  suspend fun find(id: ObjectId) = entityCollection.single(id)

  suspend fun findOrNull(id: EntityDTO) = entityCollection.singleOrNull(id)

  suspend fun find(id: EntityDTO) = entityCollection.single(id)

  suspend fun findOrNull(id: Principal) = entityCollection.singleOrNull(id)

  suspend fun find(id: Principal) = entityCollection.single(id)

  suspend fun findOrNull(id: JmongoRef<*>) = entityCollection.singleOrNull(id)

  suspend fun find(id: JmongoRef<*>) = entityCollection.single(id)

  suspend fun listByIds(ids: Iterable<String>) = entityCollection.find(idIn(ids))

  suspend fun listBy(ids: EntitiesDTO) = listByIds(ids.ids)

  suspend fun count(condition: Flow<Bson?>) = entityCollection.count(asFilter(condition))

  suspend fun count(vararg condition: Bson?) = count(condition.asFlow())

  suspend fun count(condition: Iterable<Bson?>) = count(condition.asFlow())

  suspend fun list(condition: Flow<Bson?>) = entityCollection.list(asFilter(condition))

  suspend fun list(vararg condition: Bson?) = list(condition.asFlow())

  suspend fun list(condition: Iterable<Bson?>) = list(condition.asFlow())
}

abstract class CurdRepositoryJmongo<T : MongoEntityBase> : CurdRepository<T> {
  @Suppress("UNCHECKED_CAST")
  override val entityClass by lazy {
    val parameterizedType = javaClass.genericSuperclass as ParameterizedType
    parameterizedType.actualTypeArguments[0] as Class<T>
  }
  override val jmongo: Jmongo by Jguice
  override val collection by lazy { jmongo.collection(entityClass) }
  override val bsonCollection by lazy { jmongo.bsonCollection(entityClass) }
  override val entityCollection by lazy { jmongo.entityCollection(entityClass) }
}
